package tr.edu.metu.is.mega.server.core;

import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Query;

/**
 * Utility class to simplify JPA queries.
 */
public final class JpaQueryUtil {

    /**
     * Hidden constructor.
     */
    private JpaQueryUtil() {
    }

    /**
     * Prepares a named query and returns single result.
     * 
     * @param entityManager EntityManager instance
     * @param queryName Query Name
     * @param queryParams Query parameters
     * @param <X> Return type
     * @return <X>
     */
    @SuppressWarnings("unchecked")
    public static <X> X findSingleResultByNamedQuery(EntityManager entityManager, String queryName, Map<String, Object> queryParams) {
        Query namedQuery = prepareNamedQuery(entityManager, queryName, queryParams, null);
        return (X) namedQuery.getSingleResult();
    }

    /**
     * Prepares a named query and returns matched records.
     * 
     * @param entityManager EntityManager instance
     * @param queryName Query Name
     * @param queryParams Query parameters
     * @param params Paging parameters
     * @param <X> Return type
     * @return <X>
     */
    @SuppressWarnings("unchecked")
    public static <X> List<X> findByNamedQuery(EntityManager entityManager, String queryName, Map<String, Object> queryParams, PagedQueryParams params) {
        Query namedQuery = prepareNamedQuery(entityManager, queryName, queryParams, params);
        return namedQuery.getResultList();
    }

    /**
     * Executes named query.
     * 
     * @param entityManager EntityManager instance
     * @param queryName Query Name
     * @param queryParams Query Parameters
     * @return int
     */
    public static int executeNamedQuery(EntityManager entityManager, String queryName, Map<String, Object> queryParams) {
        Query namedQuery = prepareNamedQuery(entityManager, queryName, queryParams, null);
        return namedQuery.executeUpdate();
    }

    /**
     * Prepares named query with given parameters.
     * 
     * @param entityManager EntityMamager instance
     * @param queryName Query name
     * @param queryParams Query Parameters
     * @param params Paging parameters
     * @return Query
     */
    private static Query prepareNamedQuery(EntityManager entityManager, String queryName, Map<String, Object> queryParams, PagedQueryParams params) {
        Query namedQuery = entityManager.createNamedQuery(queryName);

        for (Map.Entry<String, Object> queryParam : queryParams.entrySet()) {
            namedQuery = namedQuery.setParameter(queryParam.getKey(), queryParam.getValue());
        }

        if (params != null) {
            namedQuery.setMaxResults(params.getMaxResults());
            namedQuery.setFirstResult(params.getFirstResult());
        }
        return namedQuery;
    }

    /**
     * Makes a named query and returns single result.
     * 
     * @param entityManager EntityMamager instance
     * @param queryName Query name
     * @param queryParams Query parameters
     * @param <X> Return type
     * @return <X>
     */
    @SuppressWarnings("unchecked")
    public static <X> X findSingleResultByNamedNativeQuery(EntityManager entityManager, String queryName, Map<Integer, Object> queryParams) {
        Query namedQuery = prepareNamedNativeQuery(entityManager, queryName, queryParams, null);
        return (X) namedQuery.getSingleResult();
    }

    /**
     * Makes a named query and returns matched records.
     * 
     * @param entityManager EntityMamager instance
     * @param queryName Query name
     * @param queryParams Query parameters
     * @param params Paging parameters
     * @param <X> Return type
     * @return <X>
     */
    @SuppressWarnings("unchecked")
    public static <X> List<X> findByNamedNativeQuery(EntityManager entityManager, String queryName, Map<Integer, Object> queryParams,
            PagedQueryParams params) {
        Query namedQuery = prepareNamedNativeQuery(entityManager, queryName, queryParams, null);
        return namedQuery.getResultList();
    }

    /**
     * Executes named query and returns number of rows effected.
     * 
     * @param entityManager EntityMamager instance
     * @param queryName Query name
     * @param queryParams Query parameters
     * @return int number of rows effected
     */
    public static int executeNamedNativeQuery(EntityManager entityManager, String queryName, Map<Integer, Object> queryParams) {
        Query namedQuery = prepareNamedNativeQuery(entityManager, queryName, queryParams, null);
        return namedQuery.executeUpdate();
    }

    /**
     * Prepares named native query with given parameters.
     * 
     * @param entityManager EntityMamager instance
     * @param queryName Query name
     * @param queryParams Query parameters
     * @param pagedQueryParams Paging parameters
     * @return Query created query
     */
    private static Query prepareNamedNativeQuery(EntityManager entityManager, String queryName, Map<Integer, Object> queryParams,
            PagedQueryParams pagedQueryParams) {
        Query namedQuery = entityManager.createNamedQuery(queryName);
        for (Map.Entry<Integer, Object> queryParam : queryParams.entrySet()) {
            namedQuery = namedQuery.setParameter(queryParam.getKey(), queryParam.getValue());
        }
        if (pagedQueryParams != null) {
            namedQuery.setMaxResults(pagedQueryParams.getMaxResults());
            namedQuery.setFirstResult(pagedQueryParams.getFirstResult());
        }
        return namedQuery;
    }

}
